/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/

package com.genia.toolbox.web.gwt.form.client.bean.impl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.genia.toolbox.web.gwt.form.client.bean.ExecutionDelayable;
import com.genia.toolbox.web.gwt.form.client.bean.ExecutionDelayableListener;
import com.google.gwt.user.client.Command;

/**
 * base implementation of an {@link ExecutionDelayable}.
 */
public abstract class AbstractExecutionDelayable
    implements ExecutionDelayable
{

  /**
   * the commands to execute once initialized.
   */
  private final Queue<Command> pendingCommands = new LinkedList<Command>();

  /**
   * this value is equals to 1 when the object is initialized, and lower or equals to
   * 0 otherwise.
   */
  private int inited = 1;

  /**
   * the list of listeners.
   */
  private List<ExecutionDelayableListener> listeners = new ArrayList<ExecutionDelayableListener>();



  /**
   * add a {@link ExecutionDelayableListener} that will be signaled when the
   * status of the object change.
   * 
   * @param executionDelayableListener
   *          the {@link ExecutionDelayableListener} to add
   * @see ExecutionDelayable#addExecutionDelayableListener (ExecutionDelayableListener)
   */
  public void addExecutionDelayableListener(ExecutionDelayableListener executionDelayableListener)
  {
    listeners.add(executionDelayableListener);
  }



  /**
   * signal the the object is no more initialized. Each call to this method
   * *must* be followed by *exactly* one call to the method releaseInit() when
   * the object is once more initialized. If this is not the case, everything
   * can happen.
   * 
   * @see AbstractExecutionDelayable#releaseInit()
   */
  protected void blockInit()
  {
    inited--;
    if (inited == 0) {
      throwSignal();
    }
  }



  /**
   * register a {@link Command} to execute when the object is initialized.
   * 
   * @param command
   *          the command to execute when the object is initialized
   * @see ExecutionDelayable#execute(Command)
   */
  public void execute(Command command)
  {
    synchronized (this) {
      if (isInitialized() && pendingCommands.isEmpty()) {
        command.execute();
      }
      else {
        pendingCommands.add(command);
      }
    }
  }


  /**
   * execute all pending actions.
   */
  private void executeAll()
  {
    synchronized (this) {
      while (isInitialized() && !pendingCommands.isEmpty()) {
        pendingCommands.poll().execute();
      }
    }
  }



  /**
   * returns whether the object is initialized.
   * 
   * @return whether the object is initialized
   * @see com.genia.toolbox.web.gwt.form.client.bean.ExecutionDelayable#isInitialized()
   */
  public boolean isInitialized()
  {
    return inited > 0;
  }



  /**
   * method to be called when the object is initialized. This must be called
   * once when it is initialized for the first time, and then once for every
   * blockInit() begin called.
   * 
   * @see AbstractExecutionDelayable#blockInit()
   */
  protected void releaseInit()
  {
    inited++;
    if (inited == 1) {
      throwSignal();
    }
    if(inited > 1) {
      throw new RuntimeException("The state is incoherent. Inited should never be greater than 1.");
    }
  }



  /**
   * throw the signal due to a change of state to every listeners.
   */
  private void throwSignal()
  {
    for (ExecutionDelayableListener listener : new ArrayList<ExecutionDelayableListener>(listeners)) {
      listener.stateChanged(this, isInitialized());
    }
    executeAll();
  }

}
